﻿#region Copyright © 2011 Prageeth Silva <prageeth@thenewcoders.com>
/*
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the author(s) be held liable for any damages arising from
 * the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 *   1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 * 
 *   2. Altered source versions must be plainly marked as such, and must not
 *      be misrepresented as being the original software.
 * 
 *   3. This notice may not be removed or altered from any source distribution.
 */
#endregion

using System;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using SourcePlus.Formatters;
using SourcePlus.RtfPdf;
using SourcePlus.Themes;
using System.ComponentModel;

namespace SourcePlus.Core
{
    public class BatchAssembler
    {

        private RtfToPdf rtfToPdf = new RtfToPdf();

        public event ProgressChangedEventHandler ProgressChanged;

        public event AssemblyCompletedEventHandler AssemblyCompleted;

        public bool WrapText { get; set; }

        public int WrapLength { get; set; }

        public SourceTheme Theme { get; set; }

        public int TabSpaces { get; set; }
        
        public bool SkipOnError { get; set; }

        public bool OverwriteDestination { get; set; }

        public int Failed
        {
            get { return this.rtfToPdf.Failures; }
        }            

        public int Successful
        {
            get { return this.rtfToPdf.Successes; }
        }

        public string TrimDirectory
        {
            get { return this.rtfToPdf.TrimDirectory; }
            set { this.rtfToPdf.TrimDirectory = value; }
        }

        public string AddDirectory
        {
            get { return this.rtfToPdf.AddDirectory; }
            set { this.rtfToPdf.AddDirectory = value; }
        }

        public iTextSharp.text.Rectangle PageSize
        {
            get { return this.rtfToPdf.PageSize; }
            set { this.rtfToPdf.PageSize = value; }
        }

        public iTextSharp.text.Rectangle Margins
        {
            get { return this.rtfToPdf.Margins; }
            set { this.rtfToPdf.Margins = value; }
        }
        
        private static Regex rgxStartingSpaces = new Regex(@"(?<=(\s))\S+.+");
        private static Regex rgxLastCommaSpaces = new Regex(@"(?<=([,\+\-\%\*\/\\]))\s+");
        private static Regex rgxLastSpaces = new Regex(@"\s+");
                
        private static Font defaultFont = new Font("Consolas", 9);

        private BackgroundWorker worker = null;

        public BatchAssembler()
        {
            this.Theme = null;
            this.WrapLength = 90;
            this.WrapText = true;
            this.TabSpaces = 4;
            this.SkipOnError = false;
            this.OverwriteDestination = false;
        }

        public bool FormatFiles(SourceFile[] files, bool skipOnError)
        {

            if (this.Theme == null) { return false; }

            RichTextBox rtb = new RichTextBox();
            rtb.Font = defaultFont;
            rtb.DetectUrls = false;

            int len = files.Length;
            for (int i = 0; i < len; i++)
            {
                try
                {
                    if (this.FormatFile(rtb, files[i].LongName, GetFormatter(files[i].FileName)))
                    {
                        rtb.SelectAll();
                        files[i].RtfContent = rtb.SelectedRtf;
                        files[i].IsValid = true;
                    }
                    else if (!skipOnError) { return false; }
                }
                catch
                {
                    if (!skipOnError) { return false; }
                }                
            }

            return true;

        }

        public bool SaveRtfFiles(string destination, SourceFile[] files, bool onTopDirectory)
        {

            // TODO

            return true;

        }

        public bool SavePdfFiles(string destination, SourceFile[] files, bool onTopDirectory)
        {

            // TODO

            return true;

        }

        public bool SaveSinglePdf(string destination, SourceFile[] files)
        {

            rtfToPdf.Convert(destination, files, this.SkipOnError);

            return true;

        }
        
        public bool FormatAndSaveSinglePdf(string destination, SourceFile[] files)
        {

            if (this.worker != null && this.worker.IsBusy) { return false; }

            this.worker = new BackgroundWorker();
            this.worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            this.worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            this.worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            this.worker.WorkerReportsProgress = true;
            this.worker.WorkerSupportsCancellation = true;

            this.worker.RunWorkerAsync(new object[] { destination, files });
            
            return true;

        }

        public bool CancelCurrentOperation()
        {

            if (this.worker != null && this.worker.IsBusy)
            {
                this.worker.CancelAsync();
                return true;
            }

            return false;

        }

        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {

            object[] args = e.Argument as object[];
            string destination = args[0] as string;
            SourceFile[] files = args[1] as SourceFile[];

            this.worker.ReportProgress(0, "Formatting files...");

            this.FormatFiles(files, this.SkipOnError);

            // check if user has cancelled action
            if (this.worker.CancellationPending)
            {
                e.Cancel = true;
                e.Result = null;
                return;
            }

            this.worker.ReportProgress(50, "Writing files into PDF...");

            rtfToPdf.Convert(destination, files, this.SkipOnError);

            this.worker.ReportProgress(100, "Completed...");

        }

        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.OnProgressChanged(this, e);
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.OnAssemblyCompleted(this, new AssemblyCompletedEventArgs(e));
        }

        protected virtual void OnProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (this.ProgressChanged != null)
            {
                this.ProgressChanged(this, e);
            }
        }

        protected virtual void OnAssemblyCompleted(object sender, AssemblyCompletedEventArgs e)
        {
            if (this.AssemblyCompleted != null)
            {
                this.AssemblyCompleted(this, e);
            }
        }

        public bool FormatFile(RichTextBox rtb, string file, SourceFormat formatter)
        {
            return FormatFile(rtb, new FileStream(file, FileMode.Open), formatter);
        }

        public bool FormatFile(RichTextBox rtb, Stream filestream, SourceFormat formatter)
        {

            if (rtb == null || filestream == null || formatter == null) { return false; }

            // setup the formatter
            formatter.Theme = this.Theme;
            formatter.TabSpaces = this.TabSpaces;

            // clear all the contents
            rtb.Clear();

            // try to format the code
            try
            {

                // get formatted text
                rtb.SelectedRtf = formatter.FormatCode(filestream);

                // check if the lines need to be wrapped
                if (this.WrapText)
                {
                    WrapArrangeText(rtb, this.WrapLength, this.TabSpaces);
                }

                return true;

            }
            catch
            {
                return false;
            }

        }

        private static SourceFormat GetFormatter(string filename)
        {
            return GetFormatterByType(filename.Substring(filename.LastIndexOf('.') + 1));
        }

        public static SourceFormat GetFormatterByType(string type)
        {

            type = type.ToLower();

            switch (type)
            {
                case "c": return new CppFormat();
                case "cpp": return new CppFormat();
                case "h": return new CppFormat();
                case "hpp": return new CppFormat();
                case "cs": return new CSharpFormat();
                case "java": return new JavaFormat();
                case "js": return new JavaScriptFormat();
                case "jsp": return new JspFormat();
                case "html": return new HtmlFormat();
                case "sql": return new TsqlFormat();
                default: return new PlainFormat();
            }

        }

        private static void WrapArrangeText(RichTextBox rtb, int lineMaxLength, int tabSpaces)
        {

            string[] lines = rtb.Lines;
            int i = 0;
            int position = 0;

            rtb.SuspendLayout();

            while (lines.Length > i)
            {

                string line = lines[i];

                // no need to wrap? goto next line
                if (line.Length <= lineMaxLength)
                {
                    position += line.Length + 1;
                    i++;
                    continue;
                }

                // line is longer, trim and wrap                
                Match m = rgxStartingSpaces.Match(line);
                string nextLine = new string(' ', tabSpaces + ((m.Success && m.Index > 0) ? m.Index : 0));

                // get the position of the last index of white space
                MatchCollection mc = rgxLastCommaSpaces.Matches(line.Substring(0, lineMaxLength));
                int lastSpace = 0;
                int spaceLen = 1;
                if (mc.Count > 0)
                {
                    m = mc[mc.Count - 1];
                    if (m.Index > (nextLine.Length - tabSpaces))
                    {
                        lastSpace = m.Index + m.Length;
                        spaceLen = m.Length;
                    }
                }
                // check if only white spaces can be found
                else
                {
                    // check for spaces only
                    mc = rgxLastSpaces.Matches(line.Substring(0, lineMaxLength));
                    if (mc.Count > 0)
                    {
                        m = mc[mc.Count - 1];
                        if (m.Index > (nextLine.Length - tabSpaces))
                        {
                            lastSpace = m.Index + m.Length;
                            spaceLen = m.Length;
                        }
                    }
                    // it's one long word, nothing to do! goto next line
                    else
                    {
                        position += line.Length + 1;
                        i++;
                        continue;
                    }
                }

                // the line can be wrapped, so goto the position
                position += lastSpace;
                rtb.SelectionStart = position - 1;
                rtb.SelectionLength = spaceLen;

                // add a new line
                rtb.SelectedText = Environment.NewLine + nextLine;
                i++;
                lines = rtb.Lines;
                continue;

            }

            rtb.ResumeLayout();

        }
        
    }


    #region "Custom event Handlers"

    public delegate void AssemblyCompletedEventHandler(object sender, AssemblyCompletedEventArgs e);

    public class AssemblyCompletedEventArgs : ActionCompletedEventArgs
    {

        public AssemblyCompletedEventArgs(AsyncCompletedEventArgs parent)
            : base(parent)
        {

        }

        public AssemblyCompletedEventArgs(RunWorkerCompletedEventArgs parent)
            : base(parent)
        {

        }

    }

    #endregion

}
